/*******************************************************************************
 * Copyright (c) 2013, 2014 UT-Battelle, LLC.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Initial API and implementation and/or initial documentation - Jay Jay Billings,
 *   Jordan H. Deyton, Dasha Gorin, Alexander J. McCaskey, Taylor Patterson,
 *   Claire Saunders, Matthew Wang, Anna Wojtowicz
 *******************************************************************************/
package org.eclipse.ice.vibe.launcher;

import java.util.ArrayList;
import java.util.Arrays;

import javax.xml.bind.annotation.XmlRootElement;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.ice.datastructures.ICEObject.IUpdateable;
import org.eclipse.ice.datastructures.entry.DiscreteEntry;
import org.eclipse.ice.datastructures.entry.IEntry;
import org.eclipse.ice.datastructures.form.DataComponent;
import org.eclipse.ice.datastructures.form.FormStatus;
import org.eclipse.ice.datastructures.form.TableComponent;
import org.eclipse.ice.io.serializable.IIOService;
import org.eclipse.ice.io.serializable.IOService;
import org.eclipse.ice.io.serializable.IReader;
import org.eclipse.ice.item.jobLauncher.JobLauncher;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;

/**
 * <p>
 * This class inherits from JobLauncher form. It will create the Vibe launcher
 * so that it can remote execute the code.
 * </p>
 * 
 * @author Scott Forest Hull II, Andrew Bennett
 */
@XmlRootElement(name = "VibeLauncher")
public class VibeLauncher extends JobLauncher {

	/**
	 * The execution command
	 */
	private String fullExecCMD;

	/**
	 * The default CAEBAT home directory.
	 */
	private String CAEBAT_ROOT;

	/**
	 * The default IPS home directory.
	 */
	private String IPS_ROOT;

	private IIOService ioService;

	/**
	 * A nullary constructor that delegates to the project constructor.
	 */
	public VibeLauncher() {
		this(null);
	}

	/**
	 * <p>
	 * The constructor. Takes an IProject argument. Calls the super constructor
	 * on JobLauncher.
	 * </p>
	 * 
	 * @param project
	 *            <p>
	 *            The project space.
	 *            </p>
	 */
	public VibeLauncher(IProject project) {
		super(project);
	}

	/**
	 * This operations sets up some VIBE-specific information for the launcher,
	 * including the default project installation directory.
	 */
	@Override
	protected void setupItemInfo() {

		// Set the name and description of the Item
		setName("VIBE Launcher");
		setDescription("Run a VIBE simulation.");

		// Set the name of the home directory
		CAEBAT_ROOT = "/home/batsim/caebat";
		IPS_ROOT = "$IPS_ROOT";

		// Set up the necessary io services if they aren't already done.
		ioService = getIOService();
		if (ioService == null) {
			setIOService(new IOService());
			ioService = getIOService();
		}
	}

	/**
	 * <p>
	 * This operation overrides setupForm() on JobLauncher. It will setup the
	 * paths and add the locations for the remote server addresses. It will call
	 * super.setupForm() prior to setting up the executable and hostnames.
	 * </p>
	 */
	@Override
	public void setupForm() {
		String copyCase = "cp -r ${installDir}vibe/examples/case6/* .;";
		String fixSIMROOT = "sed -i.bak 's?SIM_ROOT=.*?"
				+ "SIM_ROOT='`pwd`'?g' ${inputFile};";
		// Setup the VIBE launch script
		String VIBEExec = "${installDir}ipsframework-code/install/bin/ips.py"
				+ " -a --log=temp.log --platform=" + IPS_ROOT
				+ "/workstation.conf --simulation=${inputFile};";
		// Setup the command stages. An explicit forward slash is used here, so
		// will only work on linux for now.
		fullExecCMD = copyCase + fixSIMROOT + VIBEExec;
		// Setup form
		super.setupForm();
		// Stop the launcher from trying to append the input file
		setAppendInputFlag(false);
		// Setup the executable information
		setExecutable(getName(), getDescription(), this.fullExecCMD);
		// Add localhost
		addHost("localhost", "linux x86_64", CAEBAT_ROOT);

		// Add the input files types for the BatML files
		// Setup entries
		IEntry entry = new DiscreteEntry();
		entry.setAllowedValues(Arrays.asList("true", "false"));
		entry.setDefaultValue("false");
		entry.setValue("false");
		entry.setName("Use custom key-value pair file?");
		entry.setTag("MODE");
		entry.setDescription(
				"Allows the use of a customized KV Pair file generated by ICE");

		// Add the selector to the form && make it listen for changes
		DataComponent fileComponent = (DataComponent) form.getComponent(1);
		fileComponent.addEntry(entry);
		fileComponent.retrieveEntry("Use custom key-value pair file?")
				.register(this);
		form.removeComponent(1);
		form.addComponent(fileComponent);
		update(fileComponent.retrieveEntry("Use custom key-value pair file?"));
	}

	/**
	 * <p>
	 * Overrides process by setting the executable correctly and then forwarding
	 * later. Still calls super.process(actionName) once the executable is set
	 * correctly for the workstation.conf file.
	 * </p>
	 * 
	 * @param actionName
	 *            The name of the action.
	 * 
	 * @return The status of the action
	 */
	@Override
	public FormStatus process(String actionName) {

		IReader reader = ioService.getReader("IPS");
		DataComponent fileComponent = (DataComponent) form.getComponent(1);
		IEntry inputFileEntry = fileComponent.retrieveEntry("Input File");
		IEntry kvPairFileEntry = fileComponent
				.retrieveEntry("Use custom key-value pair file?");
		IFile inputFile = project.getFile(inputFileEntry.getValue());

		// Get the Run ID that may be used to locate the simulation files
		String runID = "";
		ArrayList<IEntry> runIDMatches = reader.findAll(inputFile, "RUN_ID=.*");
		if (runIDMatches != null && !runIDMatches.isEmpty()) {
			runID = runIDMatches.get(0).getName().split("=")[1];
		}

		// Get the Case Name which may also be used to locate the simulation
		// files
		String caseName = "";
		ArrayList<IEntry> caseNameMatches = reader.findAll(inputFile,
				"SIM_NAME=.*");
		if (caseNameMatches != null && !caseNameMatches.isEmpty()) {
			caseName = caseNameMatches.get(0).getName().split("=")[1];
		}
		// Determine if we need to use the Run ID or the Case Name to find the
		// files
		if (caseName.contains("${RUN_ID}")) {
			caseName = runID;
		}

		// Get the base path for the simulation files
		String dataDir = "";
		ArrayList<IEntry> simRootMatches = reader.findAll(inputFile,
				"SIM_ROOT=.*");
		if (simRootMatches != null && !simRootMatches.isEmpty()) {
			dataDir = simRootMatches.get(0).getName().split("=")[1];
		}

		// TODO Check if it is ever valid for one of these to be empty but not
		// the other. Change from AND to OR if not.
		// Check if a case is specified in the input file. Create an error
		// dialog if not.
		if (dataDir.isEmpty() && caseName.isEmpty()) {

			PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
				@Override
				public void run() {
					ErrorDialog.openError(new Shell(Display.getDefault()),
							"Error Reading VIBE Input File",
							"SIM not specified in input.",
							new Status(IStatus.ERROR,
									"org.eclipse.ice.vibe.launcher",
									"Required attributes SIM_NAME and SIM_ROOT are "
											+ "not specified in input file."));
					return;
				}
			});
			return FormStatus.InfoError;
		}

		if (dataDir.endsWith("/$SIM_NAME")) {
			dataDir = dataDir.substring(0, dataDir.length() - 10);
		} else if (dataDir.endsWith("${SIM_NAME}")) {
			dataDir = dataDir.substring(0, dataDir.length() - 12);
		} else if (dataDir.endsWith(caseName)) {
			dataDir = dataDir.substring(0,
					dataDir.length() - (caseName.length() + 1));
		}

		// If we are supplying a new KV Pair file replace it in the input file
		update(fileComponent.retrieveEntry("Use custom key-value pair file?"));
		String setKVPerms = "";
		String backupKVFile = "";
		String mvKVPairFile = "";
		if (kvPairFileEntry.getValue() != "false") {
			String kvFileName = fileComponent
					.retrieveEntry("Key-value pair file").getValue();
			// writer.replace(inputFile, "input_keyvalue", kvFileName);
			setKVPerms = "chmod 775 " + kvFileName + " && ";
			backupKVFile = "mv input/input_keyvalue input/input_keyvalue.bak && ";
			mvKVPairFile = "mv " + kvFileName + " input/input_keyvalue && ";
		}

		// Pull some information from the form
		TableComponent hostTable = (TableComponent) form.getComponent(4);
		CAEBAT_ROOT = hostTable.getRow(0).get(2).getValue();

		// Set up the execution command
		String exportRoot = "export CAEBAT_ROOT=" + CAEBAT_ROOT
				+ "/vibe/components && ";
		String copyCase = "cp -r " + dataDir + "/" + caseName + "/* . && ";
		String fixSIMROOT = "sed -i.bak 's?SIM_ROOT\\ *=\\ *.*?"
				+ "SIM_ROOT\\ =\\ '`pwd`'?g' ${inputFile} && ";

		// The main execution of the simulation.
		String VIBEExec = "${installDir}ipsframework-code/install/bin/ips.py"
				+ " -a --log=temp.log --platform=" + CAEBAT_ROOT
				+ "/vibe/examples/config/batsim.conf"
				+ " --simulation=${inputFile}; ";
		fullExecCMD = exportRoot + copyCase + setKVPerms + backupKVFile
				+ mvKVPairFile + fixSIMROOT + VIBEExec;

		// Setup the executable information
		setExecutable(getName(), getDescription(), this.fullExecCMD);

		return super.process(actionName);
	}

	/**
	 * Override of update so that the VibeLauncher can check if the user wants
	 * to select a custom KV Pair file.
	 */
	@Override
	public void update(IUpdateable component) {
		refreshProjectSpace();
		if (component instanceof IEntry) {
			// Determine whether the file selector needs to be added to or
			// removed from the form
			if (component.getName() == "Use custom key-value pair file?"
					&& ((IEntry) component).getValue() == "true") {
				addInputType("Key-value pair file", "keyValueFile",
						"Key-value pair with case parameters", ".dat");
			} else if (component.getName() == "Use custom key-value pair file?"
					&& ((IEntry) component).getValue() == "false") {
				DataComponent fileComponent = (DataComponent) form
						.getComponent(1);
				fileComponent.deleteEntry("Key-value pair file");
			}
		}
	}

	/**
	 * Recursively copies a directory to a destination. This method is used to
	 * pull the simulation input files into the ICE Launch directory.
	 * 
	 * @param src
	 *            The directory to copy over
	 * @param dest
	 *            Where to put the directory
	 */
	public void copyInputDirectory(String src, String dest) {
		copyDirectory(src, dest);
	}
}